package ai.people.platform.archives.service.impl;

import ai.people.core.feign.api.elastic.ElasticsearchClient;
import ai.people.core.grpc.lib.key.account.IntimacyServiceGrpc;
import ai.people.core.grpc.lib.key.account.KeyAccount;
import ai.people.core.grpc.lib.key.account.KeyAccountRpcServiceGrpc;
import ai.people.core.grpc.lib.key.account.Request;
import ai.people.core.grpc.lib.warning.AccountInfoGrpcRequest;
import ai.people.core.grpc.lib.warning.WarningAccountServiceGrpc;
import ai.people.core.grpc.utils.StreamObserverDelegate;
import ai.people.core.nebula.aspect.NebulaAutoRelease;
import ai.people.core.nebula.entity.NebulaGraph;
import ai.people.core.nebula.factory.NebulaSessionFactoryBean;
import ai.people.core.nebula.holder.NebulaSessionContextHolder;
import ai.people.core.nebula.util.NebulaQueryUtils;
import ai.people.netmon.framework.constant.GraphDirection;
import ai.people.netmon.framework.domain.archives.request.InformationSearchRequest;
import ai.people.netmon.framework.domain.archives.vo.InformationDataBasicFieldVO;
import ai.people.netmon.framework.domain.archives.vo.InformationSearchRespListVO;
import ai.people.netmon.framework.domain.common.SimpleNameValueRespVO;
import ai.people.netmon.framework.domain.gang.construction.entity.RelationLibConfig;
import ai.people.netmon.framework.domain.gang.construction.entity.RelationRule;
import ai.people.netmon.framework.domain.graph.FrontGraphBase;
import ai.people.netmon.framework.domain.graph.FrontGraphBaseV2;
import ai.people.netmon.framework.domain.graph.KeyAccountBaseTag;
import ai.people.netmon.framework.domain.graph.enums.GraphEdgeEnum;
import ai.people.netmon.framework.domain.graph.enums.GraphTagEnum;
import ai.people.netmon.framework.domain.graph.tags.KeyAccountSearchTag;
import ai.people.netmon.framework.domain.graph.tags.KeyAccountTag;
import ai.people.netmon.framework.domain.key.account.anno.NeedParse;
import ai.people.netmon.framework.domain.key.account.enums.SearchNodeTypeEnum;
import ai.people.netmon.framework.domain.key.account.request.CollisionRequest;
import ai.people.netmon.framework.domain.key.account.request.ExtendRequest;
import ai.people.netmon.framework.domain.key.account.request.SearchRequest;
import ai.people.netmon.framework.domain.key.account.vo.CollisionResultVO;
import ai.people.netmon.framework.domain.key.account.vo.ExtendVO;
import ai.people.netmon.framework.domain.key.account.vo.KeyAccountGraph;
import ai.people.netmon.framework.domain.key.account.vo.SearchListVO;
import ai.people.netmon.framework.domain.searchmsg.bean.EsUserTotalFields;
import ai.people.netmon.framework.domain.searchmsg.request.KeyAccountSearchRequest;
import ai.people.netmon.framework.domain.warning.request.account.AccountRegisterInfoRequest;
import ai.people.netmon.framework.exception.enums.CommonEnum;
import ai.people.netmon.framework.exception.enums.GraphExceptionEnum;
import ai.people.netmon.framework.exception.enums.RelationLibExceptionEnum;
import ai.people.netmon.framework.exception.type.BusinessException;
import ai.people.netmon.framework.model.response.PageResponse;
import ai.people.netmon.framework.model.response.Result;
import ai.people.netmon.framework.utils.AssertUtils;
import ai.people.netmon.utils.NumeralPool;
import ai.people.netmon.utils.StringPool;
import ai.people.netmon.utils.idworker.IdWorker;
import ai.people.platform.archives.intimacy.IntimacyHandlerFactory;
import ai.people.platform.archives.intimacy.IntimacyServiceFactory;
import ai.people.platform.archives.service.GraphAdminService;
import ai.people.platform.archives.service.InformationSearchService;
import ai.people.platform.archives.service.KeyAccountRelationService;
import ai.people.platform.archives.service.RelationLibConfigService;
import cn.hutool.crypto.SecureUtil;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.google.protobuf.BoolValue;
import com.vesoft.nebula.Path;
import com.vesoft.nebula.Step;
import com.vesoft.nebula.client.graph.data.*;
import com.vesoft.nebula.client.graph.exception.IOErrorException;
import com.vesoft.nebula.client.graph.net.SessionWrapper;
import io.grpc.stub.StreamObserver;
import lombok.extern.slf4j.Slf4j;
import net.devh.boot.grpc.client.inject.GrpcClient;
import net.devh.boot.grpc.server.service.GrpcService;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.client.RestTemplate;

import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.util.*;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.function.Function;
import java.util.stream.Collectors;

import static java.util.stream.Collectors.toMap;

/**
 * @author zhenglin
 * @date 2022/7/26
 */
@Slf4j
@GrpcService
@NebulaAutoRelease
public class KeyAccountRelationServiceImpl extends KeyAccountRpcServiceGrpc.KeyAccountRpcServiceImplBase implements KeyAccountRelationService {
    @Autowired
    GraphAdminService graphAdminService;
    @Autowired
    RestTemplate restTemplate;
    @Autowired
    RelationLibConfigService relationLibConfigService;
    @Autowired
    NebulaSessionFactoryBean sessionFactory;
    @Autowired
    private ElasticsearchClient esFeignClient;
    @GrpcClient(value = "NM-SERVICE-WARNING")
    WarningAccountServiceGrpc.WarningAccountServiceBlockingStub warningAccountServiceBlockingStub;
    @GrpcClient(value = "DATA-SERVICE-INTIMACY")
    IntimacyServiceGrpc.IntimacyServiceBlockingStub intimacyServiceBlockingStub;
    @Autowired
    InformationSearchService informationSearchService;
    @Value("${information.thirdparty.keyAccountService}")
    private List<String> informationSearchNames;
    @Autowired
    ExecutorService pool;

    /**
     * 关系图谱-查看图
     *
     * @param id id
     * @return {@link KeyAccountGraph}
     * @throws Exception 异常
     */
    @Override
    public KeyAccountGraph getGraph(Long id) throws Exception {
        return getKeyAccountGraph(id, GraphTagEnum.KEY_ACCOUNT.vidPrefix, KeyAccountTag.class);
    }

    private KeyAccountGraph getKeyAccountGraph(Long id, String rootVidPrefix, Class tClass) throws Exception {
        String rootVid = NebulaQueryUtils.buildVid(rootVidPrefix, id);
        NebulaGraph path = graphAdminService.path(rootVid, GraphDirection.BOTH);

        KeyAccountGraph vo = new KeyAccountGraph();
        vo.setKeyAccountId(id);

        Set<FrontGraphBaseV2.Edge> edges = new HashSet<>();
        Set<FrontGraphBaseV2.Node> nodes = new HashSet<>();

        Set<NebulaGraph.NebulaPath> paths = path.getPaths();
        //根据路径构造边和节点
        for (NebulaGraph.NebulaPath nebulaPath : paths) {
            NebulaGraph.NebulaNode from = nebulaPath.getFrom();
            String fromVid = (String) from.getVid();

            NebulaGraph.NebulaNode to = nebulaPath.getTo();
            String toVid = (String) to.getVid();
            //构造起始节点
            KeyAccountGraph.Node fromNode = buildNodeVo(from, rootVid, tClass);
            nodes.add(fromNode);
            //构造结束节点
            KeyAccountGraph.Node toNode = buildNodeVo(to, rootVid, tClass);
            nodes.add(toNode);
            //构造边
            KeyAccountGraph.Edge edgeVo = buildEdgeVo(fromVid, toVid, false);
            //重写hashcode以判重
            if (edges.contains(edgeVo)) {
                edges.remove(edgeVo);
                edgeVo = buildEdgeVo(fromVid, toVid, true);
            }
            edges.add(edgeVo);
        }
        if (CollectionUtils.isEmpty(nodes)) {
            NebulaGraph.NebulaNode rootNode = path.getRootNode();
            KeyAccountGraph.Node node = buildNodeVo(rootNode, rootVid, tClass);
            nodes.add(node);
        }
        vo.setNodes(nodes);
        vo.setEdges(edges);

        return vo;
    }

    private <T extends KeyAccountBaseTag> KeyAccountGraph.Node buildNodeVo(NebulaGraph.NebulaNode nebulaNode, String rootVid, Class<T> tClass) {
        KeyAccountGraph.Node nodeVo = new KeyAccountGraph.Node();
        if (nebulaNode == null) {
            return nodeVo;
        }
        String vid = (String) nebulaNode.getVid();
        Map<String, Object> properties = nebulaNode.getProperties();
        nodeVo.setId(vid);

        T tag = JSONObject.parseObject(JSONObject.toJSONString(properties), tClass);
        String extType = tag.getExtType();
        properties = JSONObject.parseObject(JSONObject.toJSONString(tag), new TypeReference<HashMap<String, Object>>() {
        });

        nodeVo.setNodesType(tag.getNodeType());
        nodeVo.setPid((String) properties.getOrDefault("pid", ""));
        nodeVo.setProperties(properties);
        nodeVo.setMain(Objects.equals(rootVid, vid));
        nodeVo.setType(extType);
        nodeVo.setX(tag.getX());
        nodeVo.setY(tag.getY());

        return nodeVo;
    }

    private KeyAccountGraph.Edge buildEdgeVo(String fromVid, String toVid, boolean startArrow) {
        KeyAccountGraph.Edge edgeVo = new KeyAccountGraph.Edge();
        edgeVo.setSource(fromVid);
        edgeVo.setTarget(toVid);
        edgeVo.setStartArrow(startArrow);
        edgeVo.setEndArrow(true);
        return edgeVo;
    }

    /**
     * 关系图谱-更新图
     *
     * @param vo 签证官
     */
    @Override
    public void updateGraph(KeyAccountGraph vo) throws Exception {
        SessionWrapper sessionWrapper = sessionFactory.getObject();
        Set<FrontGraphBaseV2.Node> nodes = vo.getNodes();
        Set<FrontGraphBaseV2.Edge> edges = vo.getEdges();

        Long keyAccountId = vo.getKeyAccountId();
        String rootVid = NebulaQueryUtils.buildVid(GraphTagEnum.KEY_ACCOUNT.vidPrefix, keyAccountId);
        //先删除图
        boolean delete = NebulaQueryUtils.deleteByGoRoot(sessionWrapper, rootVid, 1, 50, null);
        AssertUtils.isTrue(delete, CommonEnum.FAIL);
        //保存图
        saveEachNode(nodes, edges, sessionWrapper, rootVid, GraphTagEnum.KEY_ACCOUNT);
    }

    private void saveEachNode(Set<FrontGraphBaseV2.Node> nodes, Set<FrontGraphBaseV2.Edge> edges, SessionWrapper session, String rootVid, GraphTagEnum tagEnum) throws IOErrorException, UnsupportedEncodingException {
        //按vid聚合
        Map<String, FrontGraphBaseV2.Node> groupByVid = nodes.stream().collect(toMap(FrontGraphBaseV2.Node::getId, Function.identity()));

        //记录已经保存了的节点
        Map<String, String> alreadySaveNodes = new HashMap<>(nodes.size());
        //4、遍历边保存节点
        for (FrontGraphBaseV2.Edge edge : edges) {
            String sourceVid = saveNode(session, rootVid, groupByVid, edge.getSource(), alreadySaveNodes, tagEnum);
            String targetVid = saveNode(session, rootVid, groupByVid, edge.getTarget(), alreadySaveNodes, tagEnum);
            saveEdge(session, edge, sourceVid, targetVid);
        }
    }

    private String saveNode(SessionWrapper session, String rootVid, Map<String, FrontGraphBaseV2.Node> groupByVid, Object source, Map<String, String> alreadySaveNodes, GraphTagEnum tagEnum) throws IOErrorException, UnsupportedEncodingException {
        FrontGraphBaseV2.Node node = groupByVid.get(source);
        String nodeId = node.getId();
        if (alreadySaveNodes.containsKey(nodeId)) {
            return alreadySaveNodes.get(nodeId);
        }
        Map<String, Object> properties = node.getProperties();
        String pid = node.getPid();
        String vid = node.isMain() ? rootVid : nodeId;

        alreadySaveNodes.put(nodeId, vid);

        properties.put("ext_type", node.getType());
        properties.put("pid", pid);
        //坐标
        properties.put("x", node.getX());
        properties.put("y", node.getY());
        Object data = JSONObject.parseObject(JSONObject.toJSONString(properties), tagEnum.clazz);
        boolean b = NebulaQueryUtils.insertVertex(session, vid, tagEnum.tagName, data);

        AssertUtils.isTrue(b, CommonEnum.FAIL);
        return vid;
    }

    private String getSearchNodeVid(String rootVid, GraphTagEnum tagEnum, String extData) {
        if (StringUtils.isBlank(extData)) {
            return NebulaQueryUtils.buildVid(tagEnum.vidPrefix, IdWorker.getShortId());
        }
        return SecureUtil.md5(rootVid + extData);
    }

    private void saveEdge(SessionWrapper session, FrontGraphBaseV2.Edge edge, String sourceVid, String targetVid) throws IOErrorException, UnsupportedEncodingException {
        boolean startArrow = edge.getStartArrow() != null && edge.getStartArrow();
        boolean endArrow = edge.getEndArrow() != null && edge.getEndArrow();
        //插入边的关系
        //sourceNode <- targetNode
        if (startArrow) {
            boolean saveEdge = NebulaQueryUtils.insertEdge(session, targetVid, sourceVid, GraphEdgeEnum.BLANK.edgeName, null);
            AssertUtils.isTrue(saveEdge, CommonEnum.FAIL);
        }
        //sourceNode -> targetNode
        if (endArrow) {
            boolean saveEdge = NebulaQueryUtils.insertEdge(session, sourceVid, targetVid, GraphEdgeEnum.BLANK.edgeName, null);
            AssertUtils.isTrue(saveEdge, CommonEnum.FAIL);
        }
    }

    /**
     * 关系图谱-扩线
     *
     * @param request 请求
     * @return {@link ExtendVO}
     * @throws Exception 异常
     */
    @Override
    public ExtendVO extend(ExtendRequest request) throws Exception {
        ExtendVO res = new ExtendVO();

        String extendType = request.getExtendType();
        String extendList = request.getExtendList();

        //去关系库里查询配置的关系规则
        RelationLibConfig libConfig = relationLibConfigService.getOne(new LambdaQueryWrapper<RelationLibConfig>().eq(RelationLibConfig::getExpListValue, extendList)
                .eq(RelationLibConfig::getExpTypeValue, extendType));
        if (libConfig == null) {
            throw new BusinessException(RelationLibExceptionEnum.NOT_EXIST);
        }
        String rule = libConfig.getRule();
        res.setRule(analysisRule(rule));
        //解析规则
        Map<String, Integer> ruleMapping = handleConfigRuleMapping(rule);
        //调用计算亲密度gRPC
        List<ExtendVO.Extend> intimacyList = getIntimacyGrpc(ruleMapping, request);
        //分页
        List<ExtendVO.Extend> collect = intimacyList.stream().skip(request.getPageNo()).limit(request.getPageSize()).collect(Collectors.toList());
        PageResponse<ExtendVO.Extend> builder = PageResponse.builder(collect, 0, 0, request.getOriginPageNo());
        res.setList(builder);
        return res;
    }

    /**
     * 解析配置的规则转成字符串
     *
     * @param rule 规则
     * @return {@link String}
     */
    private String analysisRule(String rule) {
        List<RelationRule.Rule> rules = JSONArray.parseArray(rule, RelationRule.Rule.class);
        StringBuilder sb = new StringBuilder();
        for (RelationRule.Rule r : rules) {
            sb.append(r.getLNum()).append(r.getSymbol()).append(r.getName()).append(r.getNextSymbol());
        }
        return sb.toString();
    }

    private Map<String, Integer> handleConfigRuleMapping(String rule) {
        List<RelationRule.Rule> rules = JSONArray.parseArray(rule, RelationRule.Rule.class);
        return rules.stream().collect(Collectors.toMap(RelationRule.Rule::getCode, RelationRule.Rule::getLNum));
    }

    private List<ExtendVO.Extend> getIntimacyGrpc(Map<String, Integer> ruleMapping, ExtendRequest request) throws Exception {
        String extendList = request.getExtendList();
        Request grpcRequest = Request.newBuilder()
                .setOriId(request.getUsername())
                .setFindType(extendList)
                .build();

        List<?> responseList = IntimacyServiceFactory.getIntimacyResponse(intimacyServiceBlockingStub, grpcRequest, extendList);
        return IntimacyHandlerFactory.getIntimacyHandler(extendList)
                .analysis(responseList, ruleMapping, request.getUsername(), sessionFactory.getObject());
    }

    /**
     * 关系图谱-碰撞
     *
     * @param request 请求
     * @return {@link CollisionResultVO}
     */
    @Override
    public CollisionResultVO collision(CollisionRequest request) throws Exception {
        CollisionResultVO vo = new CollisionResultVO();
        SessionWrapper sessionWrapper = null;
        try {
            sessionWrapper = sessionFactory.getObject();
            String nodeId = request.getNodeId();

            String username = request.getUsername();
            String platform = request.getPlatform();
            GraphTagEnum configEnum = getConfigEnum(platform);

            String toUsername = request.getToUsername();
            NebulaSessionContextHolder.switchSpace(sessionFactory.getConnectProperties().getMetaDataName(), sessionWrapper);
            String fromVid = NebulaQueryUtils.buildVid(configEnum.vidPrefix, username);
            String toVid = NebulaQueryUtils.buildVid(configEnum.vidPrefix, toUsername);

            List<CollisionResultVO.Path> paths = findPath(sessionWrapper, nodeId, fromVid, toVid, configEnum.tagName, request.getStep());
            vo.setPaths(paths);
        } finally {
            NebulaSessionContextHolder.switchSpace(sessionFactory.getConnectProperties().getSpaceName(), sessionWrapper);
        }

        return vo;
    }

    private List<CollisionResultVO.Path> findPath(SessionWrapper sessionWrapper, String frontNodeId, String fromVid, String toVid, String tagName, int step) throws UnsupportedEncodingException, IOErrorException {
        List<CollisionResultVO.Path> pathsResult = new ArrayList<>();

        String pathQuery = "FIND ALL PATH WITH PROP FROM \"" + fromVid + "\" TO \"" + toVid + "\" OVER * BIDIRECT YIELD path AS p";
        log.info(pathQuery);
        ResultSet result = sessionWrapper.execute(pathQuery);
        NebulaQueryUtils.checkErr(result);
        int rowsSize = result.rowsSize();

        Map<String, FrontGraphBaseV2.Node> map = new HashMap<>();
        for (int i = 0; i < rowsSize; i++) {
            ResultSet.Record valueWrappers = result.rowValues(i);
            CollisionResultVO.Path pathOne = new CollisionResultVO.Path();
            LinkedHashSet<FrontGraphBaseV2.Node> pathOneNodes = new LinkedHashSet<>();
            LinkedHashSet<FrontGraphBaseV2.Edge> pathOneEdges = new LinkedHashSet<>();

            for (ValueWrapper valueWrapper : valueWrappers) {
                PathWrapper pathWrapper = valueWrapper.asPath();
                List<Relationship> relationships = pathWrapper.getRelationships();

                String decodeType = pathWrapper.getDecodeType();
                int timezoneOffset = pathWrapper.getTimezoneOffset();
                Node startNode = pathWrapper.getStartNode();
                String rootId = startNode.getId().asString();
                FrontGraphBaseV2.Node rootNode = buildNode(startNode, rootId);

                map.put(rootId, rootNode);

                Path path = valueWrapper.getValue().getPVal();

                TreeSet<String> p = new TreeSet<>();
                int size = relationships.size();
                //如果路径超过规定的长度则跳过
                if (size > step) {
                    continue;
                }
                for (int j = 0; j < relationships.size(); j++) {
                    Relationship relationship = relationships.get(j);
                    String src = relationship.srcId().asString();
                    String dst = relationship.dstId().asString();
                    //如果路径中节点包含其他类型的，终止
                    if (!tagName.equals(dst.split(StringPool.COLON)[0])) {
                        break;
                    }

                    Step stepVal = path.steps.get(j);
                    Node node = (Node) new Node(stepVal.dst).setDecodeType(decodeType).setTimezoneOffset(timezoneOffset);
                    String nodeId = node.getId().asString();
                    FrontGraphBaseV2.Node stepNode = buildNode(node, nodeId);

                    map.put(nodeId, stepNode);

                    FrontGraphBaseV2.Node from = map.get(src);
                    FrontGraphBaseV2.Node to = map.get(dst);

                    boolean equalsSrc = fromVid.equals(src);
                    boolean equalsDst = fromVid.equals(dst);
                    if (!equalsSrc) {
                        pathOneNodes.add(from);
                    }
                    if (!equalsDst) {
                        pathOneNodes.add(to);
                    }

                    String reSrc = src, reDst = dst;
                    if (equalsSrc) {
                        reSrc = frontNodeId;
                    }
                    if (equalsDst) {
                        reDst = frontNodeId;
                    }
                    FrontGraphBaseV2.Edge edge = buildEdgeVo(reSrc, reDst, false);
                    //重写hashcode以判重
                    if (pathOneEdges.contains(edge)) {
                        pathOneEdges.remove(edge);
                        edge = buildEdgeVo(reSrc, reDst, true);
                    }
                    pathOneEdges.add(edge);

                    p.add(src.split(StringPool.COLON)[1]);
                    p.add(dst.split(StringPool.COLON)[1]);
                }

                String join = StringUtils.join(p, "->");
                pathOne.setP(join);
                pathOne.setNodes(pathOneNodes);
                pathOne.setEdges(pathOneEdges);
            }
            pathsResult.add(pathOne);
        }
        return pathsResult;
    }

    private FrontGraphBaseV2.Node buildNode(Node node, String nodeId) throws UnsupportedEncodingException {
        FrontGraphBaseV2.Node n = new FrontGraphBaseV2.Node();
        n.setNodesType(CollectionUtils.isNotEmpty(node.tagNames()) ? node.tagNames().get(0) : null);
        n.setId(nodeId);
        n.setType(String.valueOf(NumeralPool.FIVE));
        n.setProperties(NebulaQueryUtils.getNodeProperties(node));
        return n;
    }

    private GraphTagEnum getConfigEnum(String platform) {
        GraphTagEnum byTagName = GraphTagEnum.getByTagName(platform);
        if (byTagName == null || byTagName.clazz == null) {
            throw new BusinessException(GraphExceptionEnum.TAG_NOT_MAPPING);
        }
        return byTagName;
    }

    /**
     * 辅助落查-查看图
     *
     * @param id id
     * @return {@link FrontGraphBase}
     */
    @Override
    public KeyAccountGraph searchGraph(Long id) throws Exception {
        return getKeyAccountGraph(id, GraphTagEnum.KEY_ACCOUNT_SEARCH.vidPrefix, KeyAccountSearchTag.class);
    }


    /**
     * 辅助落查-更新图
     *
     * @param graph 图
     */
    @Override
    public void updateSearchGraph(KeyAccountGraph graph) throws Exception {
        doUpdateGraph(graph, GraphTagEnum.KEY_ACCOUNT_SEARCH);
    }

    private void doUpdateGraph(KeyAccountGraph graph, GraphTagEnum graphEnum) throws Exception {
        SessionWrapper sessionWrapper = sessionFactory.getObject();
        Set<FrontGraphBaseV2.Node> nodes = graph.getNodes();
        Set<FrontGraphBaseV2.Edge> edges = graph.getEdges();

        Long keyAccountId = graph.getKeyAccountId();
        String rootVid = NebulaQueryUtils.buildVid(graphEnum.vidPrefix, keyAccountId);
        //先删除图
        boolean delete = NebulaQueryUtils.deleteByGoRoot(sessionWrapper, rootVid, 1, 50, null);
        AssertUtils.isTrue(delete, CommonEnum.FAIL);
        //保存图
        saveEachNode(nodes, edges, sessionWrapper, rootVid, graphEnum);
    }

    /**
     * 辅助落查-点击开始落查
     *
     * @param request 请求
     * @return {@link List}<{@link SearchListVO}>
     */
    @Override
    public List<SearchListVO> searchStartFind(SearchRequest request) throws InterruptedException {
        List<SearchListVO> result = new ArrayList<>();

        Integer pageSize = request.getPageSize();

        Long keyAccountId = request.getKeyAccountId();
        //todo 根据ID去数据库里查配置的内容
        String userName = "";
        String platform = "";
        String rootVid = NebulaQueryUtils.buildVid(GraphTagEnum.KEY_ACCOUNT_SEARCH.vidPrefix, keyAccountId);

        int pageSizeEach = pageSize / 2;
        // 构造es查询参数
        CountDownLatch countDownLatch = new CountDownLatch(2);

        //todo 如果数据源查询出来的数据和主账号的某些属性一致，需要标识pid为 root-vid
        //查询es
        pool.execute(() -> {
            try {
                queryOwnerEs(request, result, pageSizeEach,rootVid, countDownLatch);
            } catch (IllegalAccessException e) {
                throw new BusinessException(e);
            }
        });
        //社工库
        pool.execute(() -> querySocialSource(request, result, pageSize - pageSizeEach,rootVid, countDownLatch));

        countDownLatch.await();
        return result.stream().distinct().collect(Collectors.toList());
    }

    /**
     * 查询外部社工库
     *
     * @param request        请求
     * @param result         结果
     * @param pageSize       页面大小
     * @param countDownLatch 倒计时门闩
     * @param rootVid        根vid
     */
    private void querySocialSource(SearchRequest request, List<SearchListVO> result, Integer pageSize, String rootVid, CountDownLatch countDownLatch) {
        try {
            String nodeData = request.getNodeData();

            InformationSearchRequest informationSearchRequest = new InformationSearchRequest();
            informationSearchRequest.setPageNo(request.getOriginPageNo());
            informationSearchRequest.setPageSize(pageSize);
            informationSearchRequest.setSearchKeywords(nodeData);
            informationSearchRequest.setDataSourceList(informationSearchNames);

            log.info("query social source: {} ", informationSearchRequest);

            PageResponse<InformationSearchRespListVO> informationSearchRespListVOPageResponse = informationSearchService.searchV2(informationSearchRequest);
            Collection<InformationSearchRespListVO> list = informationSearchRespListVOPageResponse.getList();
            log.info("辅助落查社工库返回条数:{}", list.size());
            for (InformationSearchRespListVO informationSearchRespListVO : list) {
                SearchListVO vo = new SearchListVO();
                List<SearchListVO.Row> rows = new ArrayList<>();
                InformationDataBasicFieldVO basicField = informationSearchRespListVO.getBasicField();
                String dataId = basicField.getDataId();
                String dataSource = basicField.getDataSource();
                String pid = SecureUtil.md5(dataId + dataSource);
                vo.setPid(pid);
                List<SimpleNameValueRespVO> showFields = informationSearchRespListVO.getShowFields();
                for (SimpleNameValueRespVO showField : showFields) {
                    String name = showField.getName();
                    Object value = showField.getValue();
//                    if (platform.equals(infoType) && "user_name".equals(name) && Objects.equals(userName, o)) {
//                        pid = rootVid;
//                    }
                    if ("platform".equals(name)) {
                        vo.setType(value);
                        continue;
                    }
                    if (Objects.isNull(value)) {
                        continue;
                    }
                    String nid = SecureUtil.md5(pid + name + value);
                    rows.add(new SearchListVO.Row(nid, name, value));
                }
                vo.setRows(rows);
                result.add(vo);
            }
        } finally {
            countDownLatch.countDown();
        }
    }

    /**
     * 查询内部es数据源
     *
     * @param request        请求
     * @param pageSize       页面大小
     * @param result          试验
     * @param rootVid        根vid
     * @param countDownLatch 倒计时门闩
     * @throws IllegalAccessException 非法访问异常
     */
    private void queryOwnerEs(SearchRequest request, List<SearchListVO> result, Integer pageSize, String rootVid, CountDownLatch countDownLatch) throws IllegalAccessException {
        try {
            String nodeData = request.getNodeData();

            KeyAccountSearchRequest esQuery = new KeyAccountSearchRequest();
            esQuery.setSearchKeywords(nodeData);
            esQuery.setPageNo(request.getPageNo());
            esQuery.setPageSize(pageSize);

            log.info("query es:{}", esQuery);
            Result<PageResponse<EsUserTotalFields>> search = esFeignClient.search(esQuery);

            PageResponse<EsUserTotalFields> esResult = search.getData();

            Collection<EsUserTotalFields> data = esResult.getList();
            for (EsUserTotalFields datum : data) {
                SearchListVO vo = new SearchListVO();

                String infoType = datum.getUserType();
                vo.setType(infoType);
                String id = datum.getId();
                List<SearchListVO.Row> rows = new ArrayList<>();
                String pid = SecureUtil.md5(infoType + id);
                vo.setPid(pid);
                Field[] declaredFields = datum.getClass().getDeclaredFields();
                for (Field declaredField : declaredFields) {
                    declaredField.setAccessible(true);
                    NeedParse annotation = declaredField.getAnnotation(NeedParse.class);
                    if (annotation != null) {
                        String name = annotation.fieldName();
                        Object o = declaredField.get(datum);

//                        if (platform.equals(infoType) && "user_name".equals(name) && Objects.equals(userName,o)) {
//                            pid = rootVid;
//                        }
                        if (Objects.isNull(o)) {
                            continue;
                        }
                        String nid = SecureUtil.md5(pid + name + o);
                        rows.add(new SearchListVO.Row(nid, name, o));
                    }
                }
                vo.setRows(rows);
                result.add(vo);
            }
            log.info("辅助落查es返回条数:{}", data.size());
        } finally {
            countDownLatch.countDown();
        }
    }

    /**
     * 检查是否能更新基本信息
     *
     * @param accountRegisterInfoRequest 账户注册信息请求
     * @return boolean
     */
    @Override
    public boolean checkUpdate(AccountRegisterInfoRequest accountRegisterInfoRequest) throws Exception {
        SessionWrapper sessionWrapper = sessionFactory.getObject();
        Long accountId = accountRegisterInfoRequest.getAccountId();

        //调用详情服务
        AccountInfoGrpcRequest request = AccountInfoGrpcRequest.newBuilder()
                .setId(accountId)
                .build();
        KeyAccount keyAccount = warningAccountServiceBlockingStub.accountInfo(request);

        //查询图
        String rootVid = NebulaQueryUtils.buildVid(GraphTagEnum.KEY_ACCOUNT_SEARCH.vidPrefix, accountId);
        //对比信息
        String newPassword = accountRegisterInfoRequest.getAccountPassword();
        String newNickName = accountRegisterInfoRequest.getAccountNickName();
        String newSummary = accountRegisterInfoRequest.getAccountSummary();

        String oriPassword = keyAccount.getAccountPassword();

        List<String> upVidList = new ArrayList<>();
        if (!Objects.equals(oriPassword, newPassword)) {
            upVidList.add(getSearchNodeVid(rootVid, GraphTagEnum.KEY_ACCOUNT_SEARCH, oriPassword));
        }
        String oriNickName = keyAccount.getAccountNickName();
        if (!Objects.equals(oriNickName, newNickName)) {
            upVidList.add(getSearchNodeVid(rootVid, GraphTagEnum.KEY_ACCOUNT_SEARCH, oriNickName));
        }
        String oriAccountSummary = keyAccount.getAccountSummary();
        if (!Objects.equals(oriAccountSummary, newSummary)) {
            upVidList.add(getSearchNodeVid(rootVid, GraphTagEnum.KEY_ACCOUNT_SEARCH, oriAccountSummary));
        }

        String pathQuery = NebulaQueryUtils.buildPathQuery(rootVid, 2, null, GraphDirection.BOTH);
        NebulaGraph path = NebulaQueryUtils.path(sessionWrapper, pathQuery);

        Set<NebulaGraph.NebulaPath> paths = path.getPaths();
        Map<String, Long> fromMap = paths.stream().collect(Collectors.groupingBy(x -> (String) x.getFrom().getVid(), Collectors.counting()));
        Map<String, Long> toMap = paths.stream().collect(Collectors.groupingBy(x -> (String) x.getTo().getVid(), Collectors.counting()));
        for (String upVid : upVidList) {
            long cnt = fromMap.getOrDefault(upVid, 0L) + toMap.getOrDefault(upVid, 0L);
            if (cnt > 1) {
                return true;
            }
        }
        return false;
    }


    /**
     * <pre>
     * 保存图服务
     * </pre>
     *
     * @param request
     * @param responseObserver
     */
    @Override
    public void save2Graph(KeyAccount request, StreamObserver<BoolValue> responseObserver) {
        StreamObserverDelegate.build(responseObserver).execute(() -> {
            try {
                log.info("grpc 获取新增账号 id:{}",request.getId());
                doSaveGraph(request);
            } catch (Exception e) {
                throw new BusinessException(GraphExceptionEnum.SAVE_GRAPH_ERR);
            }
            return BoolValue.newBuilder().setValue(true).build();
        });
    }

    /**
     * <pre>
     * 删除图服务
     * </pre>
     *
     * @param request
     * @param responseObserver
     */
    @Override
    public void delGraph(KeyAccount request, StreamObserver<BoolValue> responseObserver) {
        StreamObserverDelegate.build(responseObserver).execute(() -> {
            try {
                log.info("grpc 删除账号 id:{}",request.getId());
                doDelGraph(request.getId());
            } catch (Exception e) {
                throw new BusinessException(GraphExceptionEnum.SAVE_GRAPH_ERR);
            }
            return BoolValue.newBuilder().setValue(true).build();
        });
    }

    private void doDelGraph(Long id) throws Exception {
        SessionWrapper sessionWrapper = sessionFactory.getObject();
        //删除重点账号-社交
        String keyAccountIdVid = NebulaQueryUtils.buildVid(GraphTagEnum.KEY_ACCOUNT.vidPrefix, id);
        boolean delKeyAccount = NebulaQueryUtils.deleteByGoRoot(sessionWrapper, keyAccountIdVid, 1, 50, null);
        AssertUtils.isTrue(delKeyAccount,GraphExceptionEnum.OPT_ERR);
        //删除重点账号-辅助落查
        String keyAccountSearchVid = NebulaQueryUtils.buildVid(GraphTagEnum.KEY_ACCOUNT_SEARCH.vidPrefix, id);
        boolean delKeyAccountSearch = NebulaQueryUtils.deleteByGoRoot(sessionWrapper, keyAccountSearchVid, 1, 50, null);
        AssertUtils.isTrue(delKeyAccountSearch,GraphExceptionEnum.OPT_ERR);
    }

    /**
     * 初始化保存重点账号图
     *
     * @param request 请求
     * @throws Exception 异常
     */
    private void doSaveGraph(KeyAccount request) throws Exception {
        SessionWrapper sessionWrapper = sessionFactory.getObject();
        long id = request.getId();
        //保存社交关联初始化信息到图
        saveKeyAccount(request, sessionWrapper, id);
        //保存辅助落查初始化信息到图
        saveKeyAccountSearch(request, sessionWrapper, id);
    }

    private void saveKeyAccountSearch(KeyAccount request, SessionWrapper sessionWrapper, long id) throws IOErrorException, UnsupportedEncodingException {
        //辅助落查主节点
        String rootVid = NebulaQueryUtils.buildVid(GraphTagEnum.KEY_ACCOUNT_SEARCH.vidPrefix, id);
        String account = request.getAccount();
        String platform = request.getPlatform();
        saveSearchNode(sessionWrapper, rootVid, account, platform, NumeralPool.ONE, "0");
        //属性
        String accountId = request.getAccountId();
        if (StringUtils.isNotBlank(accountId)) {
            String vid = saveSearchNode(sessionWrapper, accountId, SearchNodeTypeEnum.social_account_id.name, NumeralPool.TWO, rootVid);
            boolean b = NebulaQueryUtils.insertEdge(sessionWrapper, rootVid, vid, GraphEdgeEnum.BLANK.edgeName, null);
            AssertUtils.isTrue(b, GraphExceptionEnum.SAVE_GRAPH_ERR);
        }
        String accountPassword = request.getAccountPassword();
        if (StringUtils.isNotBlank(accountPassword)) {
            String vid = saveSearchNode(sessionWrapper, accountPassword, SearchNodeTypeEnum.social_password.name, NumeralPool.TWO, rootVid);
            boolean b = NebulaQueryUtils.insertEdge(sessionWrapper, rootVid, vid, GraphEdgeEnum.BLANK.edgeName, null);
            AssertUtils.isTrue(b, GraphExceptionEnum.SAVE_GRAPH_ERR);
        }
        String accountNickName = request.getAccountNickName();
        if (StringUtils.isNotBlank(accountNickName)) {
            String vid = saveSearchNode(sessionWrapper, accountNickName, SearchNodeTypeEnum.social_nickname.name, NumeralPool.TWO, rootVid);
            boolean b = NebulaQueryUtils.insertEdge(sessionWrapper, rootVid, vid, GraphEdgeEnum.BLANK.edgeName, null);
            AssertUtils.isTrue(b, GraphExceptionEnum.SAVE_GRAPH_ERR);
        }
        String accountSummary = request.getAccountSummary();
        if (StringUtils.isNotBlank(accountSummary)) {
            String vid = saveSearchNode(sessionWrapper, accountSummary, SearchNodeTypeEnum.social_brief.name, NumeralPool.TWO, rootVid);
            boolean b = NebulaQueryUtils.insertEdge(sessionWrapper, rootVid, vid, GraphEdgeEnum.BLANK.edgeName, null);
            AssertUtils.isTrue(b, GraphExceptionEnum.SAVE_GRAPH_ERR);
        }
    }

    private void saveSearchNode(SessionWrapper sessionWrapper, String keyAccountSearchVid, String extData, String extType, Integer nodeType, String pid) throws IOErrorException, UnsupportedEncodingException {
        KeyAccountSearchTag keyAccountSearchTag = new KeyAccountSearchTag();
        keyAccountSearchTag.setExtType(String.valueOf(nodeType));
        keyAccountSearchTag.setExtData(extData);
        keyAccountSearchTag.setNodeType(extType);
        keyAccountSearchTag.setPid(pid);

        boolean b = NebulaQueryUtils.insertVertex(sessionWrapper, keyAccountSearchVid, GraphTagEnum.KEY_ACCOUNT_SEARCH.tagName, keyAccountSearchTag);
        AssertUtils.isTrue(b, GraphExceptionEnum.SAVE_GRAPH_ERR);
    }

    private String saveSearchNode(SessionWrapper sessionWrapper, String extData, String extType, Integer nodeType, String pid) throws IOErrorException, UnsupportedEncodingException {
        String vid = NebulaQueryUtils.buildVid(GraphTagEnum.KEY_ACCOUNT_SEARCH.vidPrefix, IdWorker.getShortId());
        saveSearchNode(sessionWrapper, vid, extData, extType, nodeType, pid);
        return vid;
    }

    private void saveKeyAccount(KeyAccount request, SessionWrapper sessionWrapper, long id) throws IOErrorException, UnsupportedEncodingException {
        String keyAccountVid = NebulaQueryUtils.buildVid(GraphTagEnum.KEY_ACCOUNT.vidPrefix, id);

        KeyAccountTag keyAccountTag = new KeyAccountTag();

        keyAccountTag.setPlatform(request.getPlatform());
        keyAccountTag.setAccountId(request.getAccountId());
        keyAccountTag.setUserName(request.getAccount());
        keyAccountTag.setNickName(request.getAccountNickName());
        keyAccountTag.setUrl(request.getAccountLink());
        keyAccountTag.setPassword(request.getAccountPassword());
        keyAccountTag.setBrief(request.getAccountSummary());

        boolean b = NebulaQueryUtils.insertVertex(sessionWrapper, keyAccountVid, GraphTagEnum.KEY_ACCOUNT.tagName, keyAccountTag);
        AssertUtils.isTrue(b, GraphExceptionEnum.SAVE_GRAPH_ERR);
    }
}
